﻿//Sample from http://robrelyea.com/demos/bindingFinder
//  This program will find all Bindings in a project's XAML files and output some information about them to the console.
//Written by Rob Relyea
//Please use this sample code as you wish.

//Uses .NET 4.0 - msbuild apis, xaml apis
//Also uses XamlDom and SilverlightSchemaContext from XamlToolkit from http://code.msdn.microsoft.com/xaml
//If you'd like to build a solution that uses XamlToolkit, please download the toolkit from there, and see its licensing info.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xaml;
using System.Xaml.Schema;
using Microsoft.Xaml.Tools.Silverlight;
using Microsoft.Xaml.Tools.XamlDom;
using XamlPadHelpers;
using System.Text;

namespace BindingFinder
{
    public class Program
    {
        //TODO: need to remove some of the globals...like schemaContext
        public static XamlSchemaContext SchemaContext;
        public static ProjectData ProjectData;
        public static bool DiagMode = false;
        private static StringBuilder bindingErrors = new StringBuilder();

        public static string GetBindingErrors(string projectFile)
        {            
            ProjectData = new ProjectData(projectFile);
             
            //Get the appropriate XamlSchemaContext (type system context) based on the assemblies + targetFramework
            SchemaContext = GetSchemaContextForProject(ProjectData);

            //Since we'll be searching for bindings, ask the schemaContext for a XamlType that represents {Binding}.
            bindingXamlType = SchemaContext.GetXamlType(
                new XamlTypeName(XamlLanguage.Xaml2006Namespace + "/presentation", "Binding"));
            templateBindingXamlType = SchemaContext.GetXamlType(
                new XamlTypeName(XamlLanguage.Xaml2006Namespace + "/presentation", "TemplateBinding"));
            dataTemplateXT = SchemaContext.GetXamlType(
                new XamlTypeName(XamlLanguage.Xaml2006Namespace + "/presentation", "DataTemplate"));
            controlTemplateXT = SchemaContext.GetXamlType(
                new XamlTypeName(XamlLanguage.Xaml2006Namespace + "/presentation", "ControlTemplate"));
            frameworkTemplateXT = SchemaContext.GetXamlType(
                new XamlTypeName(XamlLanguage.Xaml2006Namespace + "/presentation", "FrameworkTemplate"));
            styleXT = SchemaContext.GetXamlType(
            new XamlTypeName(XamlLanguage.Xaml2006Namespace + "/presentation", "Style"));
            designInstanceXT = SchemaContext.GetXamlType(
                new XamlTypeName("http://schemas.microsoft.com/expression/blend/2008", "DesignInstance"));
            objectDataProviderXT = SchemaContext.GetXamlType(
                new XamlTypeName(XamlLanguage.Xaml2006Namespace + "/presentation", "ObjectDataProvider"));
            //System.Xaml-Issue: d:DataContext isn't fetchable as directive
            //designDataContextXM = schemaContext.GetXamlDirective("http://schemas.microsoft.com/expression/blend/2008", "DataContext");

            XamlXmlReaderSettings xxrs = new XamlXmlReaderSettings()
            {
                SkipXmlCompatibilityProcessing = true,
                ProvideLineInfo = true,
                LocalAssembly = ProjectData.LocalAssembly.Assembly,
                //System.Xaml-Issue - internal local types not working??
            };

            //Iterate through each XAML file in the project
            foreach (string xamlFilePath in ProjectData.XamlFiles)
            {
                bindingErrors.AppendLine("FileName: " + xamlFilePath);
                FileInfo xamlFile = ProjectData.GetFileInfoFromRelativePath(xamlFilePath);

                //Load XAML file into a XamlDom (which ships in the XamlToolkit - http://code.msdn.microsoft.com/xaml)
                XamlXmlReader xxr = new XamlXmlReader(xamlFile.FullName, SchemaContext, xxrs);
                XamlDomWriter xdw = new XamlDomWriter(SchemaContext);
                XamlServices.Transform(xxr, xdw);
                XamlDomObject rootObject = ((XamlDomObject)xdw.RootNode);
                rootObject.SchemaContext = SchemaContext;
                //Do a LinQ query on the XamlNodes in the XamlDom to find all {Bindings}
                foreach (XamlDomObject objectNode in
                    from bindings in rootObject.DescendantsAndSelf(bindingXamlType)
                    select bindings)
                {
                    ValidateBindingAndOutputMessages(objectNode, xamlFile);
                }
                foreach (XamlDomObject objectNode in
                    from templateBindings in rootObject.DescendantsAndSelf(templateBindingXamlType)
                    select templateBindings)
                {
                    ValidateBindingAndOutputMessages(objectNode, xamlFile);
                }
            }

            bindingErrors.AppendLine( string.Format("{0} binding warnings/errors, out of {1} bindings", bindingErrorCount, bindingCount));
            
            return bindingErrors.ToString();
        }
        public static XamlType bindingXamlType = null;
        public static XamlType templateBindingXamlType = null;
        public static XamlType dataTemplateXT = null;
        public static XamlType controlTemplateXT = null;
        public static XamlType frameworkTemplateXT = null;
        public static XamlType styleXT = null;
        private static XamlType designInstanceXT = null;
        public static XamlType objectDataProviderXT = null;

        private static ProjectData GetProjectDataFromHardcodedProjectPaths()
        {
            //2 hardcoded paths to project data.
            ProjectData silverlightProjectData = new ProjectData(@"C:\temp\RegisBindingSample\SilverlightApplication6\SilverlightApplication6\SilverlightApplication6.csproj");
            ProjectData dotNetProjectData = new ProjectData(@"C:\Users\rrelyea\Downloads\Pad-2010-04-19\Pad\Pad\Pad.csproj");

            return silverlightProjectData;
        }
        private static int bindingCount = 0;
        private static int bindingErrorCount = 0;

        private static void ValidateBindingAndOutputMessages(XamlDomObject objectNode, FileInfo xamlFile)
        {
            BindingData bindingData = null;
            Binding binding = null;
            TemplateBindingExtension templateBinding = null;
            bool typesMatch = false;
            string dataType = null;

            if (objectNode.Type.Name == "Binding")
            {
                binding = (Binding)GetBindingFromDomObject(objectNode);
                bindingData = binding;
                XamlType bindingsTypeContext = binding.TypeContext;
                if (bindingsTypeContext != null)
                {
                    if (bindingsTypeContext.UnderlyingType != null)
                    {
                        dataType = bindingsTypeContext.UnderlyingType.FullName;
                    }
                    else
                    {
                        dataType = bindingsTypeContext.ToString();
                    }
                }

                if (bindingsTypeContext != null || binding.Path == null)
                {
                    typesMatch = binding.CanAssignToTargetProperty();
                    if (!typesMatch && binding.Messages.Count == 0)
                    {
                        string messageString = string.Format(Binding.TypeMismatch, binding.TargetProperty.Name, binding.ResolvedPathType.Name);
                        if (!binding.Messages.ContainsKey(messageString) && binding.Messages.Count == 0)
                        {
                            binding.Messages.Add(messageString, binding.DomObject);
                        }
                    }
                }
            }
            else if (objectNode.Type.Name == "TemplateBindingExtension")
            {
                templateBinding = (TemplateBindingExtension)GetBindingFromDomObject(objectNode);
                bindingData = templateBinding;
                XamlType bindingsTypeContext = templateBinding.TypeContext;
                if (bindingsTypeContext != null)
                {
                    if (bindingsTypeContext.UnderlyingType != null)
                    {
                        dataType = bindingsTypeContext.UnderlyingType.FullName;
                    }
                    else
                    {
                        dataType = bindingsTypeContext.ToString();
                    }
                }

                if (bindingsTypeContext != null)
                {
                    typesMatch = templateBinding.CanAssignToTargetProperty();
                    if (!typesMatch)
                    {
                        string messageString = string.Format(TemplateBindingExtension.TypeMismatch, templateBinding.TargetProperty.Name, templateBinding.ResolvedPropertyType != null ? templateBinding.ResolvedPropertyType.Name : "unresolved property");
                        if (!templateBinding.Messages.ContainsKey(messageString) && templateBinding.Messages.Count == 0)
                        {
                            templateBinding.Messages.Add(messageString, templateBinding.DomObject);
                        }
                    }
                }

            }
            else
            {
                //TODO: not sure why, for a SL project, this code gets hit.
               // throw new NotSupportedException("should have found a binding or a templatebinding");
                return;
            }

            bindingCount++;
            if (!typesMatch)
            {
                // Console.ForegroundColor = ConsoleColor.Yellow;
                bindingErrorCount++;

                if (DiagMode)
                {
                    bindingErrors.AppendLine(string.Format("{4} at ({0},{1})-({2},{3})",
                        objectNode.StartLineNumber,
                        objectNode.StartLinePosition,
                        objectNode.EndLineNumber,
                        objectNode.EndLinePosition,
                        XamlServices.Save(binding)
                        ));
                    bindingErrors.AppendLine("TypeContext: " + dataType);
                    if (binding != null)
                        bindingErrors.AppendLine("ResolvedPathType: " + binding.ResolvedPathType);
                    if (templateBinding != null)
                        bindingErrors.AppendLine("ResolvedPropertyType: " + templateBinding.ResolvedPropertyType);
                    bindingErrors.AppendLine( string.Format("TargetProperty : Type -- {0} : {1}", binding.TargetProperty, binding.TargetProperty.Type.Name));
                    objectNode.FindParentObjectByType(null, true, /*acceptAssignableTypes*/false);
                    bindingErrors.AppendLine("");
                }
                string curDir = Environment.CurrentDirectory;
                string fileName = xamlFile.FullName;
                if (fileName.StartsWith(curDir))
                {
                    fileName = fileName.Substring(curDir.Length+1);
                }
                foreach (KeyValuePair<string, XamlDomObject> message in bindingData.Messages)
                {
                    int linePosition = message.Value.StartLinePosition + message.Value.Parent.Member.Name.Length + 2;
                    if (message.Value.Type.Name == "DataTemplate")
                    {
                        linePosition = message.Value.StartLinePosition;
                    }
                    bindingErrors.AppendLine( string.Format("({0},{1}): {2}",
                        message.Value.StartLineNumber,
                        linePosition,
                        message.Key
                        ));
                }

                if (DiagMode)
                {
                    bindingErrors.AppendLine("");
                }
            }
            // Console.ForegroundColor = ConsoleColor.White;
            return;
        }

        internal static BindingData GetBindingFromDomObject(XamlDomObject objectNode)
        {

            Stack<XamlType> typeStack = new Stack<XamlType>();
            //using vanillaschemacontext since silverlightschemacontext was failing to create binding.path correctly.
            XamlDomReader xdr = new XamlDomReader(objectNode, VanillaSchemaContext);
            XamlObjectWriter xow = new XamlObjectWriter(VanillaSchemaContext);
            XamlType bindingXT = VanillaSchemaContext.GetXamlType(typeof(Binding));
            XamlType relativeSourceXT = VanillaSchemaContext.GetXamlType(typeof(RelativeSource));
            XamlType staticResourceExtensionXT = VanillaSchemaContext.GetXamlType(typeof(StaticResourceExtension));
            XamlType typeExtensionXT = VanillaSchemaContext.GetXamlType(typeof(TypeExtension));
            XamlType templateBindingExtensionXT = VanillaSchemaContext.GetXamlType(typeof(TemplateBindingExtension));
            XamlMember member = null;
            while (xdr.Read())
            {
                switch (xdr.NodeType)
                {
                    case XamlNodeType.StartObject:
                        XamlType xamlType = xdr.Type;
                        if (xamlType.Name == "Binding")
                            xamlType = bindingXT;
                        else if (xamlType.Name == "RelativeSource")
                            xamlType = relativeSourceXT;
                        else if (xamlType.Name == "StaticResourceExtension")
                            xamlType = staticResourceExtensionXT;
                        else if (xamlType.Name == "TypeExtension")
                            xamlType = typeExtensionXT;
                        else if (xamlType.Name == "TemplateBindingExtension")
                            xamlType = templateBindingExtensionXT;
                        xow.WriteStartObject(xamlType);
                        typeStack.Push(xamlType);
                        break;
                    case XamlNodeType.EndObject:
                        xow.WriteEndObject();
                        typeStack.Pop();
                        break;
                    case XamlNodeType.StartMember:
                        member = xdr.Member;
                        if (xdr.Member != XamlLanguage.PositionalParameters)
                            member = typeStack.Peek().GetMember(xdr.Member.Name);
                        if (member == null)
                        {
                            bindingErrors.AppendLine( ("BINDINGFINDER ERROR: " + typeStack.Peek().Name + " type doesn't have " + xdr.Member.Name + " member defined"));
                        }
                        xow.WriteStartMember(member);
                        break;
                    case XamlNodeType.EndMember:
                        xow.WriteEndMember();
                        break;
                    case XamlNodeType.Value:
                        xow.WriteValue(xdr.Value);
                        break;
                }
            }
            BindingData bindingData = xow.Result as BindingData;
            bindingData.DomObject = objectNode;
            return bindingData;
        }

        private static XamlSchemaContext GetSchemaContextForProject(ProjectData projectData)
        {
            List<Assembly> appRefAssemblies = new List<Assembly>();

            foreach (AssemblyData assemData in projectData.AssemblyReferences)
            {
                assemData.Load();
                //ISSUE: With reflectiononlyload assemblies, get method not impl. SLXSC issue.
                appRefAssemblies.Add(assemData.Assembly);
            }

            XamlSchemaContext schemaContext;
            //ISSUE: got exception from this next line if any assemblies are not Silverlight
            //XamlSchemaContext schemaContext = SilverlightAssemblyHelper.CreateSilverlightSchemaContext(appAssemblyList);
            if (projectData.TargetFrameworkIdentifier == "Silverlight")
            {
                schemaContext = new SilverlightSchemaContext(appRefAssemblies);
            }
            else
            {
                //schemaContext = new XamlSchemaContext(appRefAssemblies);
                Assembly pf = Assembly.Load("PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
                Type xr = pf.GetType("System.Windows.Markup.XamlReader");
                MethodInfo method = xr.GetMethod("GetWpfSchemaContext");
                schemaContext = (XamlSchemaContext)method.Invoke(null, null);
            }
            return schemaContext;
        }
        internal static XamlSchemaContext VanillaSchemaContext = new XamlSchemaContext();
        internal static string GetString(XamlDomItem domItem)
        {
            XamlDomValue domValue = (XamlDomValue)domItem;
            return ((string)domValue.Value);
        }
    }
}
